{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "<center>\n",
    "<img src=\"https://github.com/Yorko/mlcourse.ai/blob/master/img/ods_stickers.jpg?raw=true\" />\n",
    "    \n",
    "# [mlcourse.ai](https://mlcourse.ai) – Open Machine Learning Course \n",
    "\n",
    "Auteur: [Yury Kashnitsky](https://yorko.github.io). Traduit et édité par [Christina Butsko](https://www.linkedin.com/in/christinabutsko/), [Yuanyuan Pao](https://www.linkedin.com/in/yuanyuanpao/), [Anastasia Manokhina](https://www.linkedin.com/in/anastasiamanokhina), Sergey Isaev, [Artem Trunov](https://www.linkedin.com/in/datamove/) et [Ousmane Cissé](https://github.com/oussou-dev). Ce matériel est soumis aux termes et conditions de la licence [Creative Commons CC BY-NC-SA 4.0] (https://creativecommons.org/licenses/by-nc-sa/4.0/). L'utilisation gratuite est autorisée à des fins non commerciales."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "# <center> Thème 1. Analyse exploratoire de données avec la librairie Pandas\n",
    "\n",
    "<img src=\"https://github.com/Yorko/mlcourse.ai/blob/master/img/pandas.jpg?raw=true\"  width=50% />"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "toc": true
   },
   "source": [
    "<h1>Sommaire de l'article<span class=\"tocSkip\"></span></h1>\n",
    "<div class=\"toc\"><ul class=\"toc-item\"><li><span><a href=\"#1.-Mise-en-pratique-des-principales-méthodes-de-Pandas\" data-toc-modified-id=\"1.-Mise-en-pratique-des-principales-méthodes-de-Pandas-1\">1. Mise en pratique des principales méthodes de Pandas</a></span><ul class=\"toc-item\"><li><span><a href=\"#Tri\" data-toc-modified-id=\"Tri-1.1\">Tri</a></span></li><li><span><a href=\"#Indexation-et-récupération-de-données\" data-toc-modified-id=\"Indexation-et-récupération-de-données-1.2\">Indexation et récupération de données</a></span></li><li><span><a href=\"#Application-de-fonctions-à-des-cellules,-des-colonnes-et-des-lignes\" data-toc-modified-id=\"Application-de-fonctions-à-des-cellules,-des-colonnes-et-des-lignes-1.3\">Application de fonctions à des cellules, des colonnes et des lignes</a></span></li><li><span><a href=\"#Agrégation-de-données\" data-toc-modified-id=\"Agrégation-de-données-1.4\">Agrégation de données</a></span></li><li><span><a href=\"#Tableaux-récapitulatifs\" data-toc-modified-id=\"Tableaux-récapitulatifs-1.5\">Tableaux récapitulatifs</a></span></li><li><span><a href=\"#Opérations-de-transformations-d'un-DataFrame\" data-toc-modified-id=\"Opérations-de-transformations-d'un-DataFrame-1.6\">Opérations de transformations d'un DataFrame</a></span></li></ul></li><li><span><a href=\"#2.-Prévision-du-churn-(taux-d'attrition)\" data-toc-modified-id=\"2.-Prévision-du-churn-(taux-d'attrition)-2\">2. Prévision du churn (taux d'attrition)</a></span></li><li><span><a href=\"#3.-Mission-pour-s'exercer\" data-toc-modified-id=\"3.-Mission-pour-s'exercer-3\">3. Mission pour s'exercer</a></span></li><li><span><a href=\"#4.-Ressources-utiles\" data-toc-modified-id=\"4.-Ressources-utiles-4\">4. Ressources utiles</a></span></li></ul></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 1. Mise en pratique des principales méthodes de Pandas\n",
    "Bien ... Il existe des dizaines de tutoriels intéressants sur la librairie Pandas et l'analyse visuelle des données. Si vous êtes déjà familiarisé avec ces sujets, vous pouvez passer au 3ème article de la série, dans lequel nous abordons le machine learning (apprentissage automatique).\n",
    "\n",
    "**[Pandas](http://pandas.pydata.org)** est une bibliothèque Python qui fournit des moyens étendus pour l’analyse de données. Les Data scientistes travaillent souvent avec des données stockées dans des formats sous forme de table de données telles que `.csv`,` .tsv` ou `.xlsx`. Pandas est très pratique pour charger, traiter et analyser ces données tabulaires à l’aide de requêtes quasi-similaires aux reques de type SQL. En complément de `Matplotlib` et` Seaborn`, `Pandas` offre un large éventail d'opportunités d'analyse visuelle des données tabulaires.\n",
    "\n",
    "Les pricipales structures de données dans `Pandas` sont implémentées avec les classes **Series** et **DataFrame**. Le premier est un tableau unidimensionnel indexé d'un type de données fixe. Le second est une structure de données bidimensionnelle - une table - dans laquelle chaque colonne contient des données du même type. Vous pouvez le voir comme un dictionnaire de plusieurs `Series`. Les `DataFrames` sont parfaits pour représenter des données réelles : les lignes correspondent aux instances (exemples, observations, individus etc.) et les colonnes correspondent aux caractéristiques de ces instances (variables)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-09-15T20:51:25.725973Z",
     "start_time": "2019-09-15T20:51:18.418535Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "pd.set_option(\"display.precision\", 2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous allons tester les principales méthodes en analysant un jeu de données ou [dataset](https://bigml.com/user/francisco/gallery/dataset/5163ad540c0b5e5b22000383) sur le taux de désabonnement des clients d'opérateurs téléphoniques. Lisons les données (en utilisant la méthode `read_csv`), et jetons un coup d’œil aux 5 premières lignes en utilisant la méthode` head`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-09-15T20:58:48.483494Z",
     "start_time": "2019-09-15T20:58:47.885539Z"
    }
   },
   "outputs": [],
   "source": [
    "url = (\n",
    "    \"https://raw.githubusercontent.com/Yorko/mlcourse.ai/master/data/telecom_churn.csv\"\n",
    ")\n",
    "df = pd.read_csv(url)\n",
    "\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "<details>\n",
    "<summary>Affichage des DataFrames dans Jupyter notebooks</summary>\n",
    "<p>\n",
    "Dans les notebooks Jupyter, les DataFrames sont affichés comme ces jolies tables vues ci-dessus alors que `print(df.head())` est moins bien formaté. \n",
    "Par défaut, Pandas affiche 20 colonnes et 60 lignes, donc, si votre DataFrame est plus grand, utilisez la fonction `set_option` comme dans l'exemple ci-dessous : \n",
    "    \n",
    "<br>\n",
    "    \n",
    "```\n",
    "pd.set_option('display.max_columns', 100)\n",
    "pd.set_option('display.max_rows', 100)\n",
    "```\n",
    "</p>\n",
    "</details>\n",
    "\n",
    "Rappelez-vous que chaque ligne correspond à un client, à une **instance** et les colonnes sont les **caractéristiques** de cette instance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Examinons la dimensionnalité des données, les noms des caractéristiques et les types de caractéristiques."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La sortie indique que la table contient 3333 lignes et 20 colonnes.\n",
    "\n",
    "Essayons maintenant d’afficher les noms de colonnes en utilisant `columns`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.columns)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous pouvons utiliser la méthode `info()` pour générer des informations générales sur le Dataframe :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(df.info())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "`bool`,` int64`, `float64` et` object` sont les types de données de nos caractéristiques. Nous voyons qu'une caractéristique est logique (`bool`), 3 caractéristiques sont de type ` objet`, et 16 caractéristiques sont numériques. Avec cette même méthode, nous pouvons facilement voir s’il manque des valeurs. Ici, il n'y en a pas car chaque colonne contient 3333 observations, le même nombre de lignes que nous avons vu auparavant avec `shape`.\n",
    "\n",
    "Nous pouvons **changer le type de colonne** avec la méthode `astype`. Appliquons cette méthode à la caractéristique `Churn` pour la convertir en` int64`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"Churn\"] = df[\"Churn\"].astype(\"int64\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La méthode `describe` affiche les caractéristiques statistiques de base de chaque caractéristique numérique (type ` int64` et `float64`): nombre de valeurs non manquantes, moyenne, écart-type, amplitude, médiane, (1er : 0,25) et (3ème : 0,75) quartiles."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Afin de voir les statistiques des caractéristiques non numériques, il faut indiquer explicitement ces types de données dans le paramètre `include`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.describe(include=[\"object\", \"bool\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Pour les caractéristiques catégorielles (type `objet`) et booléennes (type` bool`), nous pouvons utiliser la méthode `value_counts`. Jetons un coup d'oeil à la distribution de `Churn`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"Churn\"].value_counts()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "2850 utilisateurs sur 3333 sont *fidèles* clients; leur valeur `Churn` est 0. Pour calculer les pourcentages, passez ` normalize = True` à la fonction `value_counts`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"Churn\"].value_counts(normalize=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Tri\n",
    "\n",
    "Un `DataFrame` peut être trié selon la valeur de l’une des variables (colonnes). Par exemple, nous pouvons trier par *Total day charge*  \n",
    "(utilisez `ascending = False` pour trier par ordre décroissant):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.sort_values(by=\"Total day charge\", ascending=False).head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous pouvons également trier sur plusieurs colonnes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.sort_values(by=[\"Churn\", \"Total day charge\"], ascending=[True, False]).head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Indexation et récupération de données\n",
    "\n",
    "Un `DataFrame` peut être indexé de différentes manières.\n",
    "\n",
    "Pour obtenir une seule colonne, vous pouvez saisir : `DataFrame['NomDeColonne'] `. Que nous utilisons pour répondre à une question à propos de cette colonne uniquement: **quelle est la proportion d'utilisateurs qui se sont désabonnés dans notre base de données?**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"Churn\"].mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "14,5% est en fait assez mauvais pour une entreprise; un tel taux de désabonnement peut entraîner la faillite de l'entreprise.\n",
    "\n",
    "**L'indexation booléenne** avec une colonne est également très pratique. La syntaxe est `df[P(df['Nom']]]`, où `P` est une condition logique vérifiée pour chaque élément de la colonne ` NomDeColonne`. Le résultat de cette indexation est le `DataFrame` composé uniquement de lignes satisfaisant la condition ` P` de la colonne `NomDeColonne`.\n",
    "\n",
    "Exemple d'utlisation pour répondre à la question:\n",
    "\n",
    "**Quelles sont les valeurs moyennes des caractéristiques numériques pour les utilisateurs désabonnés, c'est-à-dire qui ont un Churn égal à 1 ?**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[df[\"Churn\"] == 1].mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "**Combien de temps (en moyenne) les utilisateurs désabonnés passent-ils au téléphone pendant la journée?**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[df[\"Churn\"] == 1][\"Total day minutes\"].mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "**Quelle est la durée maximale des appels internationaux parmi les clients fidèles (`Churn == 0`) n'ayant pas de forfait international?**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[(df[\"Churn\"] == 0) & (df[\"International plan\"] == \"No\")][\"Total intl minutes\"].max()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Les DataFrames peuvent être indexés par nom de colonne (étiquette) ou nom de ligne (index) ou par le numéro de série (indice) d'une ligne. La méthode `loc` est utilisée pour **l'indexation par nom**, tandis que` iloc() `est utilisée pour **l'indexation par numéro**.\n",
    "\n",
    "Dans le premier cas ci-dessous, nous *\"récupérons les valeurs des lignes d'index de 0 à 5 (inclus) et des colonnes étiquetées de  State à Area code (inclus)\"*.  \n",
    "Dans le second cas, nous *\"récupérons les valeurs des cinq premières lignes des trois premières colonnes\"* (comme dans un slicing avec Python : la valeur maximale n'est pas incluse)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.loc[0:5, \"State\":\"Area code\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.iloc[0:5, 0:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Si nous avons besoin de la première ou de la dernière ligne du dataframe, nous pouvons utiliser la syntaxe : `df[:1]` ou `df[-1:]`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[-1:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Application de fonctions à des cellules, des colonnes et des lignes\n",
    "\n",
    "**Pour appliquer des fonctions à chaque colonne, utilisez `apply ()`:**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.apply(np.max)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La méthode `apply` peut également être utilisée pour appliquer une fonction à chaque ligne. Pour ce faire, spécifiez `axis = 1`. Les fonctions Lambda sont très pratiques dans de tels scénarios. Par exemple, si nous devons sélectionner tous les _state_ commençant par 'W', nous pouvons le faire comme suit:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[df[\"State\"].apply(lambda state: state[0] == \"W\", axis=1)].head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "La méthode `map` peut être utilisée pour **remplacer des valeurs dans une colonne** en transmettant un dictionnaire de la forme` {ancienne_valeur: nouvelle_valeur} `comme argument:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {\"No\": False, \"Yes\": True}\n",
    "df[\"International plan\"] = df[\"International plan\"].map(d)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Presque la même chose peut être faite avec la méthode `replace`.\n",
    "\n",
    "<details>\n",
    "<summary>Différence dans le traitement des valeurs absentes dans le dictionnaire de mappage</summary>\n",
    "<p>\n",
    "Il y a une petite différence.  \n",
    "La méthode `replace` ne fera rien avec des valeurs qui ne se trouvent pas dans le dictionnaire de mappage,  \n",
    "alors que `map` les changera en `NaN`).  \n",
    "<br>    \n",
    "    \n",
    "```python\n",
    "a_series = pd.Series(['a', 'b', 'c'])\n",
    "a_series.replace({'a': 1, 'b': 1}) # 1, 2, c\n",
    "a_series.map({'a': 1, 'b': 2}) # 1, 2, NaN\n",
    "```\n",
    "</p>\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.replace({\"Voice mail plan\": d})\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Agrégation de données\n",
    "\n",
    "En général, le regroupement des données dans Pandas fonctionne comme suit :"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "```python\n",
    "df.groupby(by=grouping_columns)[columns_to_show].function()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "1. Premièrement, la méthode `groupby` divise les colonnes`grouping_columns` par leurs valeurs. Ils deviennent un nouvel index dans le dataframe qui en  résulte.\n",
    "2. Ensuite, les colonnes choisies sont sélectionnées (`columns_to_show`). Si `columns_to_show` n'est pas inclus, toutes les clauses non groupby seront incluses.\n",
    "3. Enfin, une ou plusieurs fonctions sont appliquées aux groupes obtenus par colonnes sélectionnées.\n",
    "\n",
    "Voici un exemple où nous regroupons les données en fonction des valeurs de la variable `Churn` et affichons les statistiques de trois colonnes dans chaque groupe :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "columns_to_show = [\"Total day minutes\", \"Total eve minutes\", \"Total night minutes\"]\n",
    "\n",
    "df.groupby([\"Churn\"])[columns_to_show].describe(percentiles=[])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Faisons la même chose, mais légèrement différemment, en passant une liste de fonctions à `agg ()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "columns_to_show = [\"Total day minutes\", \"Total eve minutes\", \"Total night minutes\"]\n",
    "\n",
    "df.groupby([\"Churn\"])[columns_to_show].agg([np.mean, np.std, np.min, np.max])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Tableaux récapitulatifs\n",
    "\n",
    "Supposons que nous voulions voir comment les observations de notre ensemble de données sont réparties dans le contexte de deux variables - `Churn` et`International plan`. Pour ce faire, nous pouvons construire un **tableau de contingence** en utilisant la méthode `crosstab`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.crosstab(df[\"Churn\"], df[\"International plan\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.crosstab(df[\"Churn\"], df[\"Voice mail plan\"], normalize=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous pouvons constater que la plupart des utilisateurs sont fidèles et n'utilisent pas de services supplémentaires (International Plan/Voice mail).\n",
    "\n",
    "Cela ressemblera aux **tableaux croisés dynamiques** pour ceux qui connaissent Excel. Et, bien sûr, les tableaux croisés dynamiques sont implémentés dans Pandas: la méthode `pivot_table` prend les paramètres suivants:\n",
    "\n",
    "*`values` - une liste de variables pour calculer des statistiques,  \n",
    "* `index` - une liste de variables pour regrouper les données,\n",
    "* `aggfunc` - quelles statistiques nous devons calculer pour les groupes, ex. somme, moyenne, maximum, minimum ou autre chose.\n",
    "\n",
    "Examinons le nombre moyen d'appels de jour, de soir et de nuit par code régional (area code):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.pivot_table(\n",
    "    [\"Total day calls\", \"Total eve calls\", \"Total night calls\"],\n",
    "    [\"Area code\"],\n",
    "    aggfunc=\"mean\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "### Opérations de transformations d'un DataFrame\n",
    "\n",
    "Comme beaucoup d'autres choses avec Pandas, l'ajout de colonnes à un DataFrame est réalisable de plusieurs manières.\n",
    "\n",
    "Par exemple, si nous voulons calculer le nombre total d'appels pour tous les utilisateurs, créons la série `total_calls` et collons-la dans le DataFrame:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2019-09-15T20:59:31.059899Z",
     "start_time": "2019-09-15T20:59:30.883127Z"
    }
   },
   "outputs": [],
   "source": [
    "total_calls = (\n",
    "    df[\"Total day calls\"]\n",
    "    + df[\"Total eve calls\"]\n",
    "    + df[\"Total night calls\"]\n",
    "    + df[\"Total intl calls\"]\n",
    ")\n",
    "df.insert(loc=len(df.columns), column=\"Total calls\", value=total_calls)\n",
    "# le paramètre loc est le nombre de colonnes après lequel l'objet Série doit être inséré\n",
    "# nous l'initialisons à len(df.columns) pour le coller à la toute fin du dataframe\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Il est possible d’ajouter une colonne plus facilement sans créer d’instance Series intermédiaire:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"Total charge\"] = (\n",
    "    df[\"Total day charge\"]\n",
    "    + df[\"Total eve charge\"]\n",
    "    + df[\"Total night charge\"]\n",
    "    + df[\"Total intl charge\"]\n",
    ")\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Pour supprimer des colonnes ou des lignes, utilisez la méthode `drop`, en passant les index requis et le paramètre` axis` (`1` si vous supprimez des colonnes et rien ou` 0` si vous supprimez des lignes). L'argument `inplace` indique s'il faut modifier le DataFrame d'origine. Avec `inplace = False`, la méthode` drop` ne modifie pas le DataFrame existant et en renvoie un nouveau avec des lignes ou des colonnes supprimées. Avec `inplace = True`, il modifie le DataFrame."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# supprimer les colonnes qui viennent d'être créées\n",
    "df.drop([\"Total charge\", \"Total calls\"], axis=1, inplace=True)\n",
    "# et voici comment supprimer des lignes\n",
    "df.drop([1, 2]).head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "## 2. Prévision du churn (taux d'attrition)\n",
    "\n",
    "Voyons comment le taux de désabonnement est lié à la caractéristique ou variable *International plan*. Pour ce faire, nous utiliserons un tableau de contingence `` crosstab`` et également une analyse visuelle avec `Seaborn` (l'analyse visuelle sera toutefois traitée plus en détail dans le prochain article)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.crosstab(df[\"Churn\"], df[\"International plan\"], margins=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# quelques imports pour mettre en place le cadre du graphique\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# !pip install seaborn (pour installer la librairie seaborn via le notebook)\n",
    "import seaborn as sns\n",
    "\n",
    "# import de paramètres pour améliorer le rendu visuel\n",
    "sns.set()\n",
    "# Les graphiques au format Retina sont plus nets et plus lisibles\n",
    "%config InlineBackend.figure_format = 'retina'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x=\"International plan\", hue=\"Churn\", data=df);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Nous voyons qu'avec *International Plan*, le taux de désabonnement est beaucoup plus élevé, ce qui est une observation intéressante! Peut-être des dépenses importantes et mal contrôlées avec des appels internationaux sont-elles très sujettes aux conflits et suscitent l’insatisfaction des clients de l’opérateur de télécommunications.\n",
    "\n",
    "Voyons ensuite une autre fonctionnalité importante - *Customer service calls*. Faisons également un tableau de synthèse et une image."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.crosstab(df[\"Churn\"], df[\"Customer service calls\"], margins=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x=\"Customer service calls\", hue=\"Churn\", data=df);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Bien que ce ne soit pas évident dans le tableau récapitulatif, il ressort clairement du graphique ci-dessus que le taux de résiliation augmente fortement à partir de 4 appels de service après-vente.\n",
    "\n",
    "Ajoutons maintenant une variable binaire à notre DataFrame - `Customer service calls > 3` (Appels du service client> 3). Et encore une fois, voyons comment cela se rapporte au désabonnement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df[\"Many_service_calls\"] = (df[\"Customer service calls\"] > 3).astype(\"int\")\n",
    "\n",
    "pd.crosstab(df[\"Many_service_calls\"], df[\"Churn\"], margins=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x=\"Many_service_calls\", hue=\"Churn\", data=df);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Construisons une autre table de contingence qui relie *Churn* à la fois à *International plan* et à la variable nouvellement créée *Many_service_calls*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pd.crosstab(df[\"Many_service_calls\"] & df[\"International plan\"], df[\"Churn\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "lang": "fr"
   },
   "source": [
    "Par conséquent, si un nombre d'appels vers le centre de services est supérieur à 3 et que le *International Plan* est ajouté (et en prédisant Churn=0 sinon), on peut s’attendre à une précision de 85,8%. Ce nombre, 85,8%, que nous avons obtenu grâce à ce raisonnement très simple constitue un bon point de départ (*référence*) pour les autres modèles d’apprentissage automatique que nous allons construire.\n",
    "\n",
    "Au cours de ce cours, rappelez-vous qu'avant l'avènement de l'apprentissage automatique, le processus d'analyse des données ressemblait à ce que nous venons de réaliser. Récapitulatif :\n",
    "    \n",
    "- La part des clients fidèles dans l'ensemble de données est de 85,5%. Le modèle le plus \"simple\" qui prédit toujours un \"client fidèle\" sur de telles données devinera juste dans environ 85,5% des cas. C'est-à-dire que la proportion de réponses correctes (*précision*) des modèles suivants ne devrait pas être inférieure à ce nombre et qu'elle devrait être nettement supérieure;\n",
    "- A l’aide d’une simple prévision pouvant être exprimée par la formule suivante: `International plan = True & Customer Service calls > 3 => Churn = 1, else Churn = 0`, on peut s’attendre à un taux de prédiction de 85,8%, qui est juste au-dessus de 85,5%. Ensuite, nous parlerons des arbres de décision et découvrirons comment trouver de telles règles **automatiquement** uniquement sur la base des données d’entrée;\n",
    "- Nous avons obtenu ces deux bases sans appliquer l’apprentissage automatique et elles serviront de point de départ pour nos modèles ultérieurs. S'il s'avère qu'avec un effort énorme, nous n'augmentons la précision que de 0,5%, alors nous avons peut-être commis une erreur, et il suffit de nous en tenir à un simple modèle \"if-else\" avec deux conditions;\n",
    "- Avant de former des modèles complexes, il est recommandé de mélanger un peu les données, de tracer des graphiques et de vérifier des hypothèses simples. De plus, dans les applications métier de l'apprentissage automatique, on commence généralement par des solutions simples, pour ensuite expérimenter des solutions plus complexes.\n",
    "\n",
    "## 3. Mission pour s'exercer\n",
    "Pour vous entraîner avec la librairie Pandas et l’EDA (Analyse Exploratoire de données), vous pouvez remplir [cette mission](https://www.kaggle.com/kashnitsky/a1-demo-pandas-and-uci-adult-dataset) où vous analyserez des données socio-démographiques. Il s'agit juste d'une mission pour vous exerver avec sa [solution](https://www.kaggle.com/kashnitsky/a1-demo-pandas-and-uci-adult-dataset-solution).\n",
    "\n",
    "## 4. Ressources utiles\n",
    "\n",
    "* Le même notebook en mode interactif sur [Kaggle Kernel](https://www.kaggle.com/kashnitsky/topic-1-exploratory-data-analysis-with-pandas)\n",
    "* [\"Merging DataFrames with pandas\"](https://nbviewer.jupyter.org/github/Yorko/mlcourse.ai/blob/master/jupyter_english/tutorials/merging_dataframes_tutorial_max_palko.ipynb) - a tutorial by Max Plako within mlcourse.ai (full list of tutorials is [here](https://mlcourse.ai/tutorials))\n",
    "* [\"Handle different dataset with dask and trying a little dask ML\"](https://nbviewer.jupyter.org/github/Yorko/mlcourse.ai/blob/master/jupyter_english/tutorials/dask_objects_and_little_dask_ml_tutorial_iknyazeva.ipynb) - a tutorial by Irina Knyazeva within mlcourse.ai\n",
    "* Main course [site](https://mlcourse.ai), [course repo](https://github.com/Yorko/mlcourse.ai), and YouTube [channel](https://www.youtube.com/watch?v=QKTuw4PNOsU&list=PLVlY_7IJCMJeRfZ68eVfEcu-UcN9BbwiX)\n",
    "* Official Pandas [documentation](http://pandas.pydata.org/pandas-docs/stable/index.html)\n",
    "* Course materials as a [Kaggle Dataset](https://www.kaggle.com/kashnitsky/mlcourse)\n",
    "* Medium [\"story\"](https://medium.com/open-machine-learning-course/open-machine-learning-course-topic-1-exploratory-data-analysis-with-pandas-de57880f1a68) based on this notebook\n",
    "* If you read Russian: an [article](https://habrahabr.ru/company/ods/blog/322626/) on Habr.com with ~ the same material. And a [lecture](https://youtu.be/dEFxoyJhm3Y) on YouTube\n",
    "* [10 minutes to pandas](http://pandas.pydata.org/pandas-docs/stable/10min.html)\n",
    "* [Pandas cheatsheet PDF](https://github.com/pandas-dev/pandas/blob/master/doc/cheatsheet/Pandas_Cheat_Sheet.pdf)\n",
    "* GitHub repos: [Pandas exercises](https://github.com/guipsamora/pandas_exercises/) and [\"Effective Pandas\"](https://github.com/TomAugspurger/effective-pandas)\n",
    "* [scipy-lectures.org](http://www.scipy-lectures.org/index.html) — tutorials on pandas, numpy, matplotlib and scikit-learn"
   ]
  }
 ],
 "metadata": {
  "hide_input": false,
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.4"
  },
  "nbTranslate": {
   "displayLangs": [
    "*"
   ],
   "hotkey": "alt-t",
   "langInMainMenu": true,
   "sourceLang": "en",
   "targetLang": "fr",
   "useGoogleTranslate": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": false,
   "skip_h1_title": true,
   "title_cell": "Sommaire de l'article",
   "title_sidebar": "Contents",
   "toc_cell": true,
   "toc_position": {
    "height": "427px",
    "left": "65px",
    "top": "180px",
    "width": "260px"
   },
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
